/*
 * CodeInterpreter.java
 * Copyright (C) 05/21/2010, Lhrod
 * Modified      06/06/2010
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 */
package Sindarin.CodeInterpreter;

import Robot.Robot;
import java.awt.Toolkit;
import java.io.File;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Stack;
import java.util.Vector;
import Sindarin.Common.CodeFileClass;
import Sindarin.Common.FileHeader;
import Sindarin.Common.Instruction;
import Sindarin.Common.Location;
import Sindarin.Common.MacroKey;
import Sindarin.Common.MyColor;
import Sindarin.Common.Symbol;
import Sindarin.Common.SymbolTable;
import Sindarin.ScriptParser.ScriptParser;
import Sindarin.ScriptParser.ScriptParserConstants;
import Sindarin.ScriptParser.Token;
import java.awt.event.InputEvent;

/**
 *
 * @author Lhrod
 */
public class CodeInterpreter implements Runnable {

    SymbolTable sTable;
    Vector<Instruction> instV = new Vector();
    Stack stack = new Stack();
    Robot robot;
    private Location locTmp;
    private MacroKey keyTmp;
    private String strTmp;
    private Symbol simbTmp;
    private int[] teclasTmp;
    private int i;
    private int pc;
    private boolean stopped = false;
    private Thread thread = null;
    private static Calendar calendar = Calendar.getInstance();
    private static Toolkit tool = java.awt.Toolkit.getDefaultToolkit();

    /** Creates a new instance of CodeInterpreter */
    public CodeInterpreter(Vector nInst, SymbolTable table, Robot robot) {
        this.instV = nInst;
        this.sTable = table;
        thread = new Thread(this, "CodeInterpreter");
        thread.setPriority(Thread.MAX_PRIORITY);
        this.robot = robot;
        robot.setAutoDelay(4);
    }

    public CodeInterpreter(Robot robot) {
        thread = new Thread(this, "CodeInterpreter");
        thread.setPriority(Thread.MAX_PRIORITY);
        this.robot = robot;
        robot.setAutoDelay(4);
    }

    public void loadStateFile(File f) throws Exception {

        FileHeader fHeader = CodeFileClass.getHeaderFile(f);

        String pass = null;
        if (fHeader.cypher) {
            pass = CodeFileClass.getPassDialog();
        }

        HashMap r = CodeFileClass.loadFile(f, pass);
        this.instV = (Vector<Instruction>) r.get(CodeFileClass.DataStructFile.INSTVECTOR);
        this.sTable = (SymbolTable) r.get(CodeFileClass.DataStructFile.SYMBOLTABLE);
    }

    public Vector<Instruction> getInst() {
        return instV;
    }

    public void start() {
        thread.start();
    }

    public void run() {
        pc = 0;
        if (!instV.isEmpty()) {
            while ((pc < instV.size()) && (pc >= 0) && (!stopped)) {
                try {
                        Thread.sleep(5);
                    execActInst();
                } catch (InterruptedException ex) {
                    System.err.println(java.util.ResourceBundle.getBundle("Sindarin/Gui/config").getString("ERROR_PAUSE_THREAD") + ex);
                }
            }
        }
        stopped = false;
    }

    public synchronized void execActInst() throws InterruptedException {
        Instruction instAct = ((Instruction) instV.get(pc));
        switch (instAct.opcode) {
            case MOUSEMOVE:
                locTmp = (Location) stack.pop();
                robot.mouseMove(locTmp.x, locTmp.y);
                break;
            case MOUSECLICK:
                locTmp = (Location) stack.pop();
                strTmp = (String) instAct.operator;
                if (strTmp.equals(ScriptParser.BOTON1)) {
                    robot.mouseClick(locTmp.x, locTmp.y, InputEvent.BUTTON1_MASK);
                } else {
                    robot.mouseClick(locTmp.x, locTmp.y, InputEvent.BUTTON3_MASK);
                }
                break;
            case MOUSEDCLICK:
                locTmp = (Location) stack.pop();
                strTmp = (String) instAct.operator;
                if (strTmp.equals(ScriptParser.BOTON1)) {
                    robot.mouseDClick(locTmp.x, locTmp.y, InputEvent.BUTTON1_MASK);
                } else {
                    robot.mouseDClick(locTmp.x, locTmp.y, InputEvent.BUTTON3_MASK);
                }
                break;
            case MOUSEPRESS:
                locTmp = (Location) stack.pop();
                strTmp = (String) instAct.operator;
                if (strTmp.equals(ScriptParser.BOTON1)) {
                    robot.mousePress(locTmp.x, locTmp.y, InputEvent.BUTTON1_MASK);
                } else {
                    robot.mousePress(locTmp.x, locTmp.y, InputEvent.BUTTON3_MASK);
                }
                break;
            case MOUSERELEASE:
                locTmp = (Location) stack.pop();
                strTmp = (String) instAct.operator;
                if (strTmp.equals(ScriptParser.BOTON1)) {
                    robot.mouseRelease(locTmp.x, locTmp.y, InputEvent.BUTTON1_MASK);
                } else {
                    robot.mouseRelease(locTmp.x, locTmp.y, InputEvent.BUTTON3_MASK);
                }
                break;
            case KEYPRESS:
                keyTmp = (MacroKey) stack.pop();
                teclasTmp = keyTmp.getAllKey();
                for (i = 0; i < teclasTmp.length; i++) {
                    robot.keyPress(teclasTmp[i]);
                }
                break;
            case KEYRELEASE:
                keyTmp = (MacroKey) stack.pop();
                teclasTmp = keyTmp.getAllKey();
                for (i = 0; i < teclasTmp.length; i++) {
                    robot.keyRelease(teclasTmp[i]);
                }
                break;
            case KEY:
                keyTmp = (MacroKey) stack.pop();
                teclasTmp = keyTmp.getAllKey();
                for (i = 0; i < teclasTmp.length; i++) {
                    robot.key(teclasTmp[i]);
                }
                break;
            case TEXT:
                Object tmpObj = stack.pop();
                strTmp = tmpObj.toString();
                robot.text(strTmp);
                break;
            case LOADC:
                if (instAct.operator instanceof String) {
                    strTmp = (String) instAct.operator;
                    strTmp = strTmp.substring(1, strTmp.length() - 1);
                    stack.push(strTmp);
                } else {
                    stack.push(instAct.operator);
                }
                break;
            case LOAD:
                simbTmp = sTable.find((String) instAct.operator);
                stack.push(simbTmp.value);
                break;
            case STORE:
                simbTmp = sTable.find((String) instAct.operator);
                simbTmp.value = stack.pop();
                break;
            case INC:
                stack.push(new Integer(((Integer) stack.pop()).intValue() + 1));
                break;
            case DEC:
                stack.push(new Integer(((Integer) stack.pop()).intValue() - 1));
                break;
            case MULC:
                Integer res = new Integer(((Integer) instAct.operator).intValue() * ((Integer) stack.pop()).intValue());
                stack.push(res);
                break;
            case WAIT:
                long retrazo = ((Integer) stack.pop()).longValue();
                Thread.sleep(retrazo);
                break;
            case DUP:
                stack.push(stack.lastElement());
                break;
            case JUMP:
                pc = ((Integer) instAct.operator).intValue();
                return;
            case JUMPZ:
                int remo = ((Integer) stack.pop()).intValue();
                //System.out.print("pc: " + pc + " instruccion: " + instAct);
                if (remo == 0) {
                    pc = ((Integer) instAct.operator).intValue();
                    //pc = ((Integer)((Symbol)instAct.operator).value).intValue();
                    return;
                }
                break;
            case POP:
                stack.pop();
                break;
            case EXIT:
                pc = instV.size();
                return;
            case IODELAY:
                robot.setAutoDelay(((Integer) stack.pop()).intValue());
                break;
            case LOADDELAY:
                stack.push(new Integer(robot.getAutoDelay()));
                break;
            case LOADNATIVEMODE:
                stack.push(new Integer((robot.isNativeSystem() && robot.isNativeMode() && robot.isActWin()) ? 1 : 0));
                break;
            case PRINT:
                System.out.println("Print: " + stack.pop());
                break;
            case GETCOLOR:
                locTmp = (Location) stack.pop();
                stack.push(new MyColor(robot.getPixelColor(locTmp.x, locTmp.y)));
                break;
            case CMP:
                stack.push(cmp(stack.pop(), stack.pop(), ((Integer) instAct.operator).intValue()));
                break;
            case ALU:
                Object a = stack.pop();
                Object b = stack.pop();
                stack.push(alu(a, b, ((Integer) instAct.operator).intValue()));
                break;
            case CALLSUB:
                SymbolTable tmpTable = (SymbolTable) instAct.operator;
                Object tmp;
                for (i = ((Integer) stack.pop()).intValue(); i > 0; i--) {
                    tmp = stack.pop();
                    if (tmp instanceof Symbol) {
                        simbTmp = sTable.find(((Symbol) tmp).token.image);
                        tmp = simbTmp.value;
                        simbTmp = new Symbol(simbTmp.value, simbTmp.type, new Token(ScriptParser.PARAM, "$" + i));
                        simbTmp.value = tmp;
                    } else {
                        simbTmp = new Symbol(tmp, ScriptParser.PARAM, new Token(ScriptParser.PARAM, "$" + i));
                    }
                    tmpTable.newSymbol(simbTmp);
                }
                stack.push(sTable);
                sTable = tmpTable;
                break;
            case RETURN:
                SymbolTable tableTmp = (SymbolTable) stack.pop();
                Object[] tmpArray = (Object[]) instAct.operator;
                for (i = 0; i < tmpArray.length; i++) {
                    simbTmp = tableTmp.find((String) ((Object[]) tmpArray[i])[0]);
                    simbTmp.value = sTable.find("$" + ((Integer) ((Object[]) tmpArray[i])[1]).intValue()).value;
                }
                sTable = tableTmp;
                break;
            case LOADDATE:
                calendar = Calendar.getInstance();
                stack.push(new Integer(calendar.get(Calendar.DAY_OF_MONTH)));
                stack.push(new Integer(calendar.get(Calendar.MONTH)));
                stack.push(new Integer(calendar.get(Calendar.YEAR)));
                break;
            case LOADTIME:
                calendar = Calendar.getInstance();
                stack.push(new Integer(calendar.get(Calendar.SECOND)));
                stack.push(new Integer(calendar.get(Calendar.MINUTE)));
                stack.push(new Integer(calendar.get(Calendar.HOUR_OF_DAY)));
                break;
            case BEEP:
                tool.beep();
                break;
            case GETCOMPONENT:
                Object o = stack.pop();
                int component = ((Integer) stack.pop()).intValue();
                if (o instanceof Location) {
                    switch (component) {
                        case 1:
                            stack.push(new Integer(((Location) o).x));
                            break;
                        case 2:
                            stack.push(new Integer(((Location) o).y));
                            break;
                        default:
                            System.err.println(java.util.ResourceBundle.getBundle("Sindarin/Gui/config").getString("ERROR_ACCESS_COMPONEN_NUMBER1") + component + java.util.ResourceBundle.getBundle("Sindarin/Gui/config").getString("ERROR_ACCESS_COMPONEN_NUMBER2"));
                            pc = instV.size();
                            break;
                    }
                } else {
                    switch (component) {
                        case 1:
                            stack.push(new Integer(((MyColor) o).color.getRed()));
                            break;
                        case 2:
                            stack.push(new Integer(((MyColor) o).color.getGreen()));
                            break;
                        case 3:
                            stack.push(new Integer(((MyColor) o).color.getBlue()));
                            break;
                        default:
                            System.err.println(java.util.ResourceBundle.getBundle("Sindarin/Gui/config").getString("ERROR_ACCESS_COMPONEN_NUMBER1") + component + java.util.ResourceBundle.getBundle("Sindarin/Gui/config").getString("ERROR_ACCESS_COLOR_NUMBER2"));
                            pc = instV.size();
                            break;
                    }
                }
                break;
            default:
                System.err.println(java.util.ResourceBundle.getBundle("Sindarin/Gui/config").getString("ERROR_UNKNOWN_OPCODE_NO_INTERPRETABLE") + instAct);
        }
        pc++;
    }

    public Object cmp(Object a, Object b, int op) {
        switch (op) {
            case ScriptParserConstants.GREAT:
                return new Integer((((Integer) a).intValue() < ((Integer) b).intValue()) ? 1 : 0);
            case ScriptParserConstants.GREATEQUAL:
                return new Integer((((Integer) a).intValue() <= ((Integer) b).intValue()) ? 1 : 0);
            case ScriptParserConstants.LESS:
                return new Integer((((Integer) a).intValue() > ((Integer) b).intValue()) ? 1 : 0);
            case ScriptParserConstants.LESSEQUAL:
                return new Integer((((Integer) a).intValue() >= ((Integer) b).intValue()) ? 1 : 0);
            case ScriptParserConstants.EQUAL:
                if (a instanceof Integer) {
                    return new Integer((((Integer) a).intValue() == ((Integer) b).intValue()) ? 1 : 0);
                }
                if (a instanceof String) {
                    return new Integer((((String) a).equals((String) b)) ? 1 : 0);
                }
                if (a instanceof Location) {
                    return new Integer((((Location) a).equals((Location) b)) ? 1 : 0);
                }
                if (a instanceof MacroKey) {
                    return new Integer((((MacroKey) a).equals((MacroKey) b)) ? 1 : 0);
                }
                if (a instanceof MyColor) {
                    return new Integer((((MyColor) a).equals((MyColor) b)) ? 1 : 0);
                }
                break;
            case ScriptParserConstants.NOTEQUAL:
                Integer res = (Integer) cmp(a, b, ScriptParserConstants.EQUAL);
                return new Integer((res.intValue() == 0) ? 1 : 0);
            default:
                System.err.println(java.util.ResourceBundle.getBundle("Sindarin/Gui/config").getString("ERROR_UNKNOWN_INSTRUTION"));
        }
        return new Integer(0);
    }

    public Object alu(Object a, Object b, int op) {
        switch (op) {
            case ScriptParserConstants.AND:
                //return new Integer( ((((Integer)a).intValue() != 0) && (((Integer)b).intValue() != 0)) ? 1:0);
                return new Integer(((Integer) a).intValue() & ((Integer) b).intValue());
            case ScriptParserConstants.OR:
                //return new Integer( ((((Integer)a).intValue() == 1) || (((Integer)b).intValue() == 1)) ? 1:0);
                return new Integer(((Integer) a).intValue() | ((Integer) b).intValue());
            case ScriptParserConstants.ADD:
                if (a instanceof String) {
                    return new String(b + ((String) a));
                }
                if (b instanceof String) {
                    return new String((String) b + a);
                }
                if (a instanceof Integer) {
                    return new Integer(((Integer) b).intValue() + ((Integer) a).intValue());
                }
                if (a instanceof Location) {
                    return ((Location) b).add((Location) a);
                }
                if (a instanceof MacroKey) {
                    return ((MacroKey) b).add((MacroKey) a);
                }
                if (a instanceof MyColor) {
                    return ((MyColor) b).add((MyColor) a);
                }
            case ScriptParserConstants.SUB:
                if (a instanceof Integer) {
                    return new Integer(((Integer) b).intValue() - ((Integer) a).intValue());
                }
                if (a instanceof Location) {
                    return ((Location) b).sub((Location) a);
                }
                if (a instanceof MacroKey) {
                    return ((MacroKey) b).sub((MacroKey) a);
                }
                if (a instanceof MyColor) {
                    return ((MyColor) b).sub((MyColor) a);
                }
            case ScriptParserConstants.MULTIPLY:
                return new Integer(((Integer) b).intValue() * ((Integer) a).intValue());
            case ScriptParserConstants.DIV:
                return new Integer(((Integer) b).intValue() / ((Integer) a).intValue());
            case ScriptParserConstants.MOD:
                return new Integer(((Integer) b).intValue() % ((Integer) a).intValue());
            default:
                System.err.println(java.util.ResourceBundle.getBundle("Sindarin/Gui/config").getString("ERROR_UNKNOWN_ARITMETIC_INSTRUTION"));
        }
        return null;
    }

    public void stop() {
        stopped = true;
    }
}
